﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Forms;
using _7zipWrapper.Properties;
using Application = System.Windows.Forms.Application;
using MessageBox = System.Windows.MessageBox;
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;

// TODO save and load previous compression bat files... in case the user wants to repeat an operation... or schedule it with windows scheduler
// TODO on selection changed for input dir, start building a list of the files, their extension and size (in separate thread)
// so that when the user changes selected extensions we can use this in memory list to work with very fast.
// However, if the user does not change the extensions list, we will just apply the existing one on the list of files.

namespace _7zipWrapper
{
    /// <summary>
    ///   Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            LoadSettings();
        }

        #region Settings

        /// <summary>
        ///   load the saved application settings to initialise the UI
        /// </summary>
        void LoadSettings()
        {
            // for the 7zip executable, if there is no selection saved, try to guess the correct one
            if (string.IsNullOrEmpty(Settings.Default.sevenZipLocation))
            {
                string defaultLocation64Bit = Settings.Default.defaultLocation64Bit;
                string defaultLocation32Bit = Settings.Default.defaultLocation32Bit;

                if (File.Exists(defaultLocation64Bit))
                {
                    Settings.Default.sevenZipLocation = defaultLocation64Bit;
                    Settings.Default.Save();
                    sevenZipLocationTextBox.Text = defaultLocation64Bit;
                }
                else if (File.Exists(defaultLocation32Bit))
                {
                    Settings.Default.sevenZipLocation = defaultLocation32Bit;
                    Settings.Default.Save();
                    sevenZipLocationTextBox.Text = defaultLocation32Bit;
                }
                else
                {
                    MessageBox.Show(
                        "Could not locate the 7zip executable 7zip.exe, please provide a valid location for it.",
                        "7zip location needed", MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
            else
                sevenZipLocationTextBox.Text = Settings.Default.sevenZipLocation;

            if (!string.IsNullOrEmpty(Settings.Default.inputDirectory))
            {
                inputDirectoryTextBox.Text = Settings.Default.inputDirectory;
            }

            if (!string.IsNullOrEmpty(Settings.Default.outputDirectory))
            {
                outputDirectoryTextBox.Text = Settings.Default.outputDirectory;
            }

            if (!string.IsNullOrEmpty(Settings.Default.outputFileName))
            {
                outputFileNameTextBox.Text = Settings.Default.outputFileName;
            }

            if (!string.IsNullOrEmpty(Settings.Default.excludedExtensions))
            {
                excludedFileExtensionsTextBox.Text = Settings.Default.excludedExtensions;
            }

            if (!string.IsNullOrEmpty(Settings.Default.otherOptions))
            {
                otherOptionsTextBox.Text = Settings.Default.otherOptions;
            }

            if (!string.IsNullOrEmpty(Settings.Default.highCompressionLevel.ToString(CultureInfo.InvariantCulture)))
            {
                highCompressionLevelSlider.Value = Settings.Default.highCompressionLevel;
            }

            if (!string.IsNullOrEmpty(Settings.Default.lowCompressionLevel.ToString(CultureInfo.InvariantCulture)))
            {
                lowCompressionLevelSlider.Value = Settings.Default.lowCompressionLevel;
            }
        }

        void SaveSettings()
        {
            // save the current user settins
            if (File.Exists(sevenZipLocationTextBox.Text))
            {
                Settings.Default.sevenZipLocation = sevenZipLocationTextBox.Text;
            }

            if (Directory.Exists(inputDirectoryTextBox.Text))
            {
                Settings.Default.inputDirectory = inputDirectoryTextBox.Text;
            }

            if (Directory.Exists(outputDirectoryTextBox.Text))
            {
                Settings.Default.outputDirectory = outputDirectoryTextBox.Text;
            }

            if (!string.IsNullOrEmpty(outputFileNameTextBox.Text))
            {
                Settings.Default.outputFileName = outputFileNameTextBox.Text;
            }

            if (!string.IsNullOrEmpty(excludedFileExtensionsTextBox.Text))
            {
                string[] extensions = excludedFileExtensionsTextBox.Text.Split(' ');
                if (extensions.Length > 0)
                    Settings.Default.excludedExtensions = excludedFileExtensionsTextBox.Text;
            }

            if (!string.IsNullOrEmpty(otherOptionsTextBox.Text))
            {
                Settings.Default.otherOptions = otherOptionsTextBox.Text;
            }

            Settings.Default.highCompressionLevel = (int)highCompressionLevelSlider.Value;
            Settings.Default.lowCompressionLevel = (int)lowCompressionLevelSlider.Value;

            Settings.Default.Save();
        }

        void Window_Closing(object sender, CancelEventArgs e)
        {
            SaveSettings();
        }

        #endregion

        #region Location Boxes

        void sevenZipLocationButton_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog dialog = new OpenFileDialog();
            bool? result = dialog.ShowDialog();
            if (result.Value && dialog.FileName.EndsWith("7z.exe") && File.Exists(dialog.FileName))
                sevenZipLocationTextBox.Text = dialog.FileName;
        }

        void inputDirectoryButton_Click(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog folderDialog = new FolderBrowserDialog();
            DialogResult result = folderDialog.ShowDialog();
            if (result.ToString().Equals("OK"))
                inputDirectoryTextBox.Text = folderDialog.SelectedPath;
        }

        void outputDirectoryButton_Click(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog folderDialog = new FolderBrowserDialog();
            DialogResult result = folderDialog.ShowDialog();
            if (result.ToString().Equals("OK"))
                outputDirectoryTextBox.Text = folderDialog.SelectedPath;
        }

        #endregion

        List<string> distinctExtensionsFound = new List<string>();
        string error = string.Empty;
        double highCompressionValue, lowCompressionValue;
        string outputDir, outputFileName, sevenZipLocation; 

        void Compress()
        {
            # region validation checks

            // first check everything is valid, i.e. 7z.exe location, in/out folders,
            // extensions. Perhaps even check space is enough.
            if (!File.Exists(sevenZipLocationTextBox.Text))
            {
                MessageBox.Show("Could not find the 7z.exe file", "Error finding 7zip", MessageBoxButton.OK,
                                MessageBoxImage.Error);
                return;
            }

            if (!sevenZipLocationTextBox.Text.EndsWith("7z.exe"))
            {
                MessageBox.Show("Wrong filename, should be ending with 7z.exe", "Error finding 7zip",
                                MessageBoxButton.OK,
                                MessageBoxImage.Error);
                return;
            }

            if (!Directory.Exists(inputDirectoryTextBox.Text))
            {
                MessageBox.Show("Could not find input directory", "Error finding input directory", MessageBoxButton.OK,
                                MessageBoxImage.Error);
                return;
            }

            if (!Directory.Exists(outputDirectoryTextBox.Text))
            {
                MessageBox.Show("Could not find output directory", "Error finding output directory", MessageBoxButton.OK,
                                MessageBoxImage.Error);
                return;
            }

            if (string.IsNullOrEmpty(outputFileNameTextBox.Text))
            {
                MessageBox.Show("You need to specify a filename for the compressed file",
                                "Compressed file not specified", MessageBoxButton.OK,
                                MessageBoxImage.Error);
                return;
            }

            // check if target compressed file already exists
            //if (File.Exists(outputDirectoryTextBox.Text +"\\" + outputFileNameTextBox.Text))
            //{
            //    MessageBox.Show("The compressed file already exists", "Compressed file already exists", MessageBoxButton.OK,
            //                    MessageBoxImage.Error);
            //    return;
            //}

            #endregion

            highCompressionValue = highCompressionLevelSlider.Value;
            lowCompressionValue = lowCompressionLevelSlider.Value;
            outputDir = outputDirectoryTextBox.Text;
            outputFileName = outputFileNameTextBox.Text;
            sevenZipLocation = sevenZipLocationTextBox.Text;

            statusLabel.Content = "Checking files...";

            extensionsList = GetExtensionsList();

            if (!inputDirectoryTextBox.Text.EndsWith("\\"))
                inputDir += "\\";

            distinctExtensionsFound = new List<string>();
            error = string.Empty;

            startTime = Environment.TickCount;
            Task gettingDistinctExtensions = GetDistinctExtensions(); // this needs to be in a separate thread cause sometimes it takes a long time to complete. 
            gettingDistinctExtensions.ContinueWith(OnExtensionsFiltered);
        }

        List<string> extensionsList;
        string inputDir = string.Empty;
        long startTime;

        void OnExtensionsFiltered(Task task)
        {
            TimeSpan duration1 = new TimeSpan(Environment.TickCount - startTime);
            Console.WriteLine("enumerating files took " + duration1 + "(" + (Environment.TickCount - startTime) + " ms)");

            if (!string.IsNullOrEmpty(error))
            {
                MessageBox.Show(error, "Unauthorised Access", MessageBoxButton.OK, MessageBoxImage.Error);

                statusLabel.Dispatcher.BeginInvoke(new Action(delegate { statusLabel.Content = "Ready"; }));
                return;
            }

            if (distinctExtensionsFound.Count == 0)
            {
                MessageBox.Show("could not find valid extensions", "No valid extensions", MessageBoxButton.OK,
                                MessageBoxImage.Warning);
                statusLabel.Dispatcher.BeginInvoke(new Action(delegate { statusLabel.Content = "Ready"; }));
                return;
            }

            startTime = Environment.TickCount;

            // now remove leading . and if there is none, remove the entry
            for (int i = distinctExtensionsFound.Count - 1; i >= 0; i--)
            {
                if (!distinctExtensionsFound[i].StartsWith("."))
                    distinctExtensionsFound.RemoveAt(i);
                else
                    distinctExtensionsFound[i] = distinctExtensionsFound[i].TrimStart('.');
            }

            TimeSpan duration2 = new TimeSpan(Environment.TickCount - startTime);
            Console.WriteLine("finding distinct extensions took " + duration2 + "(" +
                              (Environment.TickCount - startTime) + " ms)");

            Console.WriteLine("Extensions before cross check " + extensionsList.Count);

            // from our existing list of extensions to not compress, remove whatever does not exist in our files, to make shorter commands
            for (int i = extensionsList.Count - 1; i >= 0; i--)
            {
                if (!distinctExtensionsFound.Contains(extensionsList[i]))
                    extensionsList.RemoveAt(i);
            }

            Console.WriteLine("Extensions after cross check " + extensionsList.Count);

            foreach (var file in extensionsList)
            {
                Console.Write("{0} ", file);
            }

            // change to the correct directory
            string command0 = @"cd " + inputDir + Environment.NewLine;
            command0 += @"cd .." + Environment.NewLine + Environment.NewLine;

            // first we will deal with the files we actually want to compress, with high compression
            command0 += "@echo Using compression " + highCompressionValue + Environment.NewLine;

            string exe = "\"" + sevenZipLocation + "\"";
            string command1 = " a -r -mx=" + highCompressionValue + " ";
            command1 += outputDir + "\\" + outputFileName + " ";
            command1 += "\"" + inputDir + "\" ";

            // excluding already compressed files, based on their extension
            command1 = extensionsList.Aggregate(command1,
                                                (current, extension) => current + ("-x!" + "*." + extension + " "));

            // create a bat file with all the commands. This can also be useful for debugging
            string bat = command0 + exe + command1 + Environment.NewLine + "@echo Using compression " +
                         lowCompressionValue + Environment.NewLine;

            if (extensionsList.Count > 0)
            {
                // inlcuding only the already compressed files, based on their extension
                string command2 = " a -r -mx=" + lowCompressionValue + " ";
                command2 += outputDir + "\\" + outputFileName + " ";

                string lastDir =
                    inputDir.Substring(
                        inputDir.LastIndexOf("\\", StringComparison.Ordinal) + 1);

                command2 = extensionsList.Aggregate(command2,
                                                    (current, extension) =>
                                                    current + ("-i!\"" + lastDir + "\\" + "*." + extension + "\" "));

                bat += exe + command2 + Environment.NewLine + Environment.NewLine;
                bat += "@Pause" + Environment.NewLine;
            }

            string batFile = Application.StartupPath + "\\" + "compress.bat";
            using (StreamWriter sw = new StreamWriter(batFile))
            {
                sw.Write(bat);
            }

            // then execute in console, not hidden to communicate progress
            // by default the console runs in a separate thread, leaving the UI responsive.
            Run(string.Empty, batFile, inputDir + "\\");
        }

        void compressButton_Click(object sender, RoutedEventArgs e)
        {
            Compress();
        }

        Task GetDistinctExtensions()
        {
            error = string.Empty;
            return Task.Factory.StartNew(() =>
            {
                try
                {
                    DirectoryInfo di = new DirectoryInfo(inputDir);
                    var files =
                        from i in
                            di.GetFiles("*.*", SearchOption.AllDirectories).AsParallel()
                        select Path.GetExtension(i.Name);
                    distinctExtensionsFound = files.Distinct().ToList();
                }
                catch (UnauthorizedAccessException ex)
                {
                    error = "There is at least one file/directory that you don't have access to... " + ex.Message;
                }
            }
            );
        }

        public void Run(string arguments, string exe, string workingDir)
        {
            ProcessStartInfo start = new ProcessStartInfo
                {
                    FileName = exe,
                    UseShellExecute = false,
                    RedirectStandardOutput = false,
                    RedirectStandardError = false,
                    Arguments = arguments,
                    WorkingDirectory = workingDir
                };
            // Specify exe name.
            statusLabel.Dispatcher.BeginInvoke(new Action(delegate { statusLabel.Content = "Compressing..."; }));
               
            // TODO start this in another thread, also supporting cancellation
            using (Process process = Process.Start(start))
            {
                //using (StreamReader reader = process.StandardOutput)
                //{
                //    //while (reader.Peek() != -1)
                //    //    Console.Write(Convert.ToChar(reader.Read()));

                //    string result = reader.ReadToEnd();
                //    Console.Write(result);
                //    return result;
                //}
                Console.WriteLine("Compression started");
            }

            statusLabel.Dispatcher.BeginInvoke(new Action(delegate { statusLabel.Content = "Ready"; }));
            
        }

        void selectExcludedExtentionsButton_Click(object sender, RoutedEventArgs e)
        {
            // first parse the currently displayed extensions, and create a list of them
            List<string> displayedExtensionsList = GetExtensionsList();

            for (int i = displayedExtensionsList.Count - 1; i >= 0; i--) // remove empty entries
                if (string.IsNullOrEmpty(displayedExtensionsList[i]))
                    displayedExtensionsList.RemoveAt(i);

            ExtensionsWindow extensionsWindow = new ExtensionsWindow(displayedExtensionsList) { Owner = this, ShowInTaskbar = false };
            bool? result = extensionsWindow.ShowDialog();

            if (result == null || !result.Value)
                return;

            displayedExtensionsList = (List<string>)System.Windows.Application.Current.Properties["extenstionsList"];
            excludedFileExtensionsTextBox.Text = string.Empty;

            foreach (string extension in displayedExtensionsList)
            {
                excludedFileExtensionsTextBox.Text += extension + " ";
            }
        }

        List<string> GetExtensionsList()
        {
            string[] extensions = excludedFileExtensionsTextBox.Text.Split(' ');
            return
                extensions.Select(extension => extension.Trim()).ToList().Where(s => !string.IsNullOrEmpty(s)).ToList();
        }

        private void previewButton_Click(object sender, RoutedEventArgs e)
        {
            // TODO implement some sort of statistic for the files that need to be processed and display it 
        }

        private void inputDirectoryTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
        {
            inputDir = inputDirectoryTextBox.Text;
        }
    }
}